// by wangh
#include "camera_ts.h"

#include <fcntl.h>
#include <poll.h>
#include <pthread.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>

#include <encoder_muxing/encoder/base_encoder.h>

static void* _commu_routine(void* arg) {
  CameraTs* ts = static_cast<CameraTs*>(arg);
  while (ts->Valid()) {
    int fd = ts->GetCommuFd();
    if (fd < 0)
      continue;
    struct pollfd clientfds;
    clientfds.fd = fd;
    clientfds.events = POLLIN | POLLERR;
    clientfds.revents = 0;
    int p_ret = poll(&clientfds, 1, 50);
    if (p_ret < 0 || !(clientfds.revents & POLLIN))
      continue;
    char str[128];
    ssize_t ret = read(fd, str, sizeof str);
    if (ret <= 0) {
      fprintf(stderr, "client read from commu fd failed, errno: %d\n", errno);
      continue;
    }
    if (!strcmp(str, "live:start_stream")) {
      BaseVideoEncoder* ve = (BaseVideoEncoder*)ts->get_video_encoder();
      if (ve)
        ve->SetForceIdrFrame();
      ts->SetRunning();
    } else if (!strcmp(str, "live:stop_stream")) {
      BaseVideoEncoder* ve = (BaseVideoEncoder*)ts->get_video_encoder();
      if (ve)
        ve->SetForceIdrFrame();
      ts->SetPausing();
      ts->clear_packet_list();
    } else
      fprintf(stderr, "unknown request string: %s\n", str);
  }
  pthread_exit(nullptr);
}

const char* CameraTs::kCommuPath = "/tmp/live_commu_fifo";

CameraTs::CameraTs()
    : commu_fd_(-1), state_(RUNNING), tid_(0), external_fd_(-1) {
#ifdef DEBUG
  snprintf(class_name, sizeof(class_name), "CameraTs");
#endif
  snprintf(format, sizeof(format), "mpegts");
  exit_id = MUXER_IMMEDIATE_EXIT;
}

CameraTs::~CameraTs() {
  ClosePipe(commu_fd_);
  state_ = EXIT;
  valid_ = false;
  StopThread(tid_);
  ClosePipe(external_fd_);
}

int CameraTs::StartThread(pthread_t& tid, void* (*start_routine)(void*)) {
  if (tid)
    return -1;
  int ret = 0;
  pthread_attr_t attr;
  if (pthread_attr_init(&attr)) {
    printf("pthread_attr_init failed!\n");
    return -1;
  }
  int stacksize = 64 * 1024;
  if (pthread_attr_setstacksize(&attr, stacksize)) {
    printf("pthread_attr_setstacksize <%d> failed!\n", stacksize);
    ret = -1;
    goto out;
  }
  if (pthread_create(&tid, &attr, start_routine, this))
    printf("pthread create failed!\n");
out:
  if (pthread_attr_destroy(&attr))
    printf("pthread_attr_destroy failed!\n");
  return ret;
}

void CameraTs::StopThread(pthread_t& tid) {
  if (tid) {
    pthread_join(tid, nullptr);
    tid = 0;
  }
}

int CameraTs::OpenPipe(const char* pipe_path) {
  int fd = -1;
  if (pipe_path) {
    // If do not exist, create it.
    if (access(pipe_path, F_OK) == -1 && mkfifo(pipe_path, 0777) != 0) {
      printf("mkfifo %s failed, errno: %d\n", pipe_path, errno);
      return -1;
    }
    fd = open(pipe_path, O_RDWR);
    if (fd < 0)
      printf("open %s fail, errno: %d\n", pipe_path, errno);
  }
  return fd;
}

void CameraTs::ClosePipe(int& fd) {
  if (fd >= 0 && close(fd))
    fprintf(stderr, "close fd <%d> failed, errno: %d\n", fd, errno);
  fd = -1;
}

int CameraTs::OpenLivePipe(const char* pipe_path) {
  external_fd_ = OpenPipe(pipe_path);
  commu_fd_ = OpenPipe(kCommuPath);
  if (external_fd_ >= 0 && commu_fd_ >= 0 &&
      !StartThread(tid_, _commu_routine)) {
    return 0;
  } else {
    ClosePipe(commu_fd_);
    state_ = EXIT;
    valid_ = false;
    StopThread(tid_);
    ClosePipe(external_fd_);
    return -1;
  }
}

int CameraTs::init_uri(char* uri, int rate) {
  // av_log_set_level(AV_LOG_TRACE);
  if (!strncmp(uri, "rtsp", 4)) {
    snprintf(format, sizeof(format), "rtsp");
  } else if (!strncmp(uri, "rtp", 3)) {
    snprintf(format, sizeof(format), "rtp_mpegts");
  } else if (!strncmp(uri, "pipe", 4)) {
    char* pipe_path = uri + 7;  // pipe://
    if (OpenLivePipe(pipe_path) < 0)
      return -1;
    sprintf(uri, "pipe:%d", external_fd_);
    snprintf(format, sizeof(format), "h264");
    state_ = ENTER;
  }
  int ret = CameraMuxer::init_uri(uri, rate);
  max_video_num = rate;  // 1 seconds
  valid_ = true;
  return ret;
}

void CameraTs::stop_current_job() {
  CameraMuxer::stop_current_job();
  free_packet_list();
}

static inline ssize_t pipe_write(int fd, void* buf, size_t buf_size) {
  size_t remain_size = buf_size;
  do {
    ssize_t ret = write(fd, buf, remain_size);
    if (ret != remain_size && errno != EAGAIN) {
      printf(
          "write pipe failed, expectly write %lu bytes, really write %ld "
          "bytes\n",
          buf_size, buf_size - remain_size);
      return -1;
    }
    assert(ret >= 0);
    remain_size -= ret;
  } while (remain_size > 0);
  return buf_size;
}

// Copy from ffmpeg.
static const uint8_t* find_startcode_internal(const uint8_t* p,
                                              const uint8_t* end) {
  const uint8_t* a = p + 4 - ((intptr_t)p & 3);

  for (end -= 3; p < a && p < end; p++) {
    if (p[0] == 0 && p[1] == 0 && p[2] == 1)
      return p;
  }

  for (end -= 3; p < end; p += 4) {
    uint32_t x = *(const uint32_t*)p;
    //      if ((x - 0x01000100) & (~x) & 0x80008000) // little endian
    //      if ((x - 0x00010001) & (~x) & 0x00800080) // big endian
    if ((x - 0x01010101) & (~x) & 0x80808080) {  // generic
      if (p[1] == 0) {
        if (p[0] == 0 && p[2] == 1)
          return p;
        if (p[2] == 0 && p[3] == 1)
          return p + 1;
      }
      if (p[3] == 0) {
        if (p[2] == 0 && p[4] == 1)
          return p + 2;
        if (p[4] == 0 && p[5] == 1)
          return p + 3;
      }
    }
  }

  for (end += 3; p < end; p++) {
    if (p[0] == 0 && p[1] == 0 && p[2] == 1)
      return p;
  }

  return end + 3;
}

const uint8_t* find_startcode(const uint8_t* p, const uint8_t* end) {
  const uint8_t* out = find_startcode_internal(p, end);
  if (p < out && out < end && !out[-1])
    out--;
  return out;
}

int CameraTs::PipeWriteSpsPps(const uint8_t* spspps,
                              size_t spspps_size,
                              const struct timeval& time,
                              const int start_len /* 00 00 01 */) {
  assert(spspps_size > 0);
  if (!spspps || spspps_size <= 0)
    return -1;
  const uint8_t* p = spspps;
  const uint8_t* end = p + spspps_size;
  const uint8_t *nal_start = nullptr, *nal_end = nullptr;
  nal_start = find_startcode(p, end);
  for (;;) {
    // while (nal_start < end && !*(nal_start++));
    if (nal_start == end)
      break;
    nal_start += start_len;
    nal_end = find_startcode(nal_start, end);
    unsigned size = nal_end - nal_start + start_len;
    uint8_t nal_type = (*nal_start) & 0x1F;
    if (nal_type == 7 || nal_type == 8) {
      if (pipe_write(external_fd_, (void*)&time, sizeof(time)) < 0)
        return -1;
      if (pipe_write(external_fd_, &size, sizeof(size)) < 0)
        return -1;
      if (pipe_write(external_fd_, (void*)(nal_start - start_len), size) < 0)
        return -1;
    }
    nal_start = nal_end;
  }
  return 0;
}

int CameraTs::muxer_write_header(AVFormatContext* oc, char* url) {
  if (external_fd_ < 0)
    return CameraMuxer::muxer_write_header(oc, url);
  // Do the writting by ourself.
  for (int i = 0; i < oc->nb_streams; i++) {
    AVStream* st = oc->streams[i];
    AVCodecContext* codec = st ? st->codec : nullptr;
    if (codec && codec->codec_id == AV_CODEC_ID_H264) {
      struct timeval time = {0, 0};
      gettimeofday(&time, nullptr);
      if (!PipeWriteSpsPps(codec->extradata, codec->extradata_size, time, 3))
        return 0;
    }
  }
  fprintf(stderr, "Error: could not get the sps pps info\n");
  return -1;
}

int CameraTs::muxer_write_tailer(AVFormatContext* oc) {
  if (external_fd_ < 0)
    return CameraMuxer::muxer_write_tailer(oc);
  return 0;  // Do nothing.
}

int CameraTs::muxer_write_free_packet(MuxProcessor* process,
                                      EncodedPacket* pkt) {
  if (external_fd_ < 0)
    return CameraMuxer::muxer_write_free_packet(process, pkt);
  if (pkt->type == VIDEO_PACKET) {
    if (pipe_write(external_fd_, &pkt->time_val, sizeof(pkt->time_val)) < 0)
      return -1;
    unsigned size = pkt->av_pkt.size;
    if (pipe_write(external_fd_, &size, sizeof(size)) < 0)
      return -1;
    if (pipe_write(external_fd_, pkt->av_pkt.data, size) < 0)
      return -1;
  }
  pkt->unref();
  return 0;
}
